package com.freaky.widget;

import android.content.Context;
import android.os.Handler;
import android.text.InputFilter;
import android.text.InputType;
import android.text.Spanned;
import android.text.method.NumberKeyListener;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnFocusChangeListener;
import android.view.View.OnLongClickListener;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;


import com.freaky.paomian.R;

public class NumberPicker extends LinearLayout implements OnClickListener,
		OnFocusChangeListener, OnLongClickListener {

	public interface OnChangedListener {
		/**
         * @param picker The NumberPicker associated with this listener.
         * @param oldVal The previous value.
         * @param newVal The new value.
         */
        void onChanged(NumberPicker picker, int oldVal, int newVal);
	}
	/**
     * Interface used to format the number into a string for presentation
     */
    public interface Formatter {
        String toString(int value);
    }
	
    /*
     * Use a custom NumberPicker formatting callback to use two-digit
     * minutes strings like "01".  Keeping a static formatter etc. is the
     * most efficient way to do this; it avoids creating temporary objects
     * on every call to format().
     */
    public static final NumberPicker.Formatter TWO_DIGIT_FORMATTER=new NumberPicker.Formatter() {
		final StringBuilder mBuilder=new StringBuilder();
		final java.util.Formatter mFmt =new java.util.Formatter(mBuilder);
		final Object[] mArgs=new Object[1];
		
		public String toString(int value){
			mArgs[0]=value;
			mBuilder.delete(0, mBuilder.length());
			mFmt.format("%02d", mArgs);
			return mFmt.toString();
		}
	};
	
	
	private final Handler mHandler;
	private final Runnable mRunnable=new Runnable() {
		
		@Override
		public void run() {
			// TODO Auto-generated method stub
			if(mIncrement){
				changeCurrent(mCurrent+1);
				mHandler.postDelayed(this, mSpeed);
			}else if (mDecrement) {
				changeCurrent(mCurrent-1);
				mHandler.postDelayed(this, mSpeed);
			}
		}
	};
	private final EditText mText;
    private final InputFilter mNumberInputFilter;
    
    private String[] mDisplayedValues;

    /**
     * Lower value of the range of numbers allowed for the NumberPicker
     */
    private int mStart;

    /**
     * Upper value of the range of numbers allowed for the NumberPicker
     */
    private int mEnd;

    /**
     * Current value of this NumberPicker
     */
    private int mCurrent;

    /**
     * Previous value of this NumberPicker.
     */
    private int mPrevious;
    private OnChangedListener mListener;
    private Formatter mFormatter;
    private long mSpeed=300;
    
    
	private boolean mIncrement;
	private boolean mDecrement;
	
	/**
     * Create a new number picker
     * @param context the application environment
     */
    public NumberPicker(Context context) {
        this(context, null);
    }

    /**
     * Create a new number picker
     * @param context the application environment
     * @param attrs a collection of attributes
     */
	public NumberPicker(Context context, AttributeSet attrs) {
		// TODO Auto-generated constructor stub
		this(context,attrs,0);
	}


	@SuppressWarnings( { "UnusedDeclaration" })
	public NumberPicker(Context context, AttributeSet attrs, int defStyle) {
		// TODO Auto-generated constructor stub
		super(context,attrs);
		setOrientation(VERTICAL);
		LayoutInflater inflater=(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
		inflater.inflate(R.layout.number_picker,this, true);
		mHandler=new Handler();
		
		InputFilter inputFilter=new NumberPickerInputFilter();
		mNumberInputFilter=new NumberRangeKeyListener();
		
		mIncremenButton=(NumberPickerButton)findViewById(R.id.increment);
		mIncremenButton.setOnClickListener(this);
		mIncremenButton.setOnLongClickListener(this);
		mIncremenButton.setNumberPicker(this);
		
		mDecremenButton=(NumberPickerButton)findViewById(R.id.decrement);
		mDecremenButton.setOnClickListener(this);
		mDecremenButton.setOnLongClickListener(this);
		mDecremenButton.setNumberPicker(this);
		
		mText=(EditText)findViewById(R.id.timepicker_input);
		mText.setOnFocusChangeListener(this);
		mText.setFilters(new InputFilter[]{inputFilter});
		mText.setRawInputType(InputType.TYPE_CLASS_NUMBER);
		
		if(!isEnabled()){
			setEnabled(false);
		}
	}
	
	@Override
	public void setEnabled(boolean enabled){
		super.setEnabled(enabled);
		mIncremenButton.setEnabled(enabled);
		mDecremenButton.setEnabled(enabled);
		mText.setEnabled(enabled);
	}

	/**
     * Set the callback that indicates the number has been adjusted by the user.
     * @param listener the callback, should not be null.
     */
    public void setOnChangeListener(OnChangedListener listener) {
        mListener = listener;
    }

    /**
     * Set the formatter that will be used to format the number for presentation
     * @param formatter the formatter object.  If formatter is null, String.valueOf()
     * will be used
     */
    public void setFormatter(Formatter formatter) {
        mFormatter = formatter;
    }

    /**
     * Set the range of numbers allowed for the number picker. The current
     * value will be automatically set to the start.
     *
     * @param start the start of the range (inclusive)
     * @param end the end of the range (inclusive)
     */
    public void setRange(int start, int end) {
    	mStart=start;
    	mEnd=end;
    	mCurrent=start;
    	updateView();
    }
    
    public void  setCurrent(int current) {
		mCurrent=current;
		updateView();
	}
    
    /**
	 * The speed (in milliseconds) at which the numbers will scroll when the the
	 * +/- buttons are longpressed. Default is 300ms.
	 */
    public void setSpeed(long speed) {
		mSpeed=speed;
	}
    
   
    /**
     * Notifies the listener, if registered, of a change of the value of this 
     * NumberPicker
     */
    private void notifyChange() {
		if(mListener!=null){
			mListener.onChanged(this, mPrevious, mCurrent);
		}
	}
    
    /**
     * Updates the view of this NumberPicker.  If displayValues were specified
     * in {@link #setRange}, the string corresponding to the index specified by
     * the current value will be returned.  Otherwise, the formatter specified
     * in {@link setFormatter} will be used to format the number.
     */
    private void updateView() {
        /* If we don't have displayed values then use the
         * current number else find the correct value in the
         * displayed values for the current number.
         */
        if (mDisplayedValues == null) {
            mText.setText(formatNumber(mCurrent));
        } else {
            mText.setText(mDisplayedValues[mCurrent - mStart]);
        }
        mText.setSelection(mText.getText().length());
    }
    
    private String formatNumber(int value) {
        return (mFormatter != null)
                ? mFormatter.toString(value)
                : String.valueOf(value);
    }
    
    /**
     * Sets the current value of this NumberPicker, and sets mPrevious to the previous
     * value.  If current is greater than mEnd less than mStart, the value of mCurrent
     * is wrapped around.
     *
     * Subclasses can override this to change the wrapping behavior
     *
     * @param current the new value of the NumberPicker
     */
    protected void changeCurrent(int current) {
    	// Wrap around the values if we go past the start or end
    	if (current>mEnd) {
			current=mStart;
		} else if(current<mStart){
			current=mEnd;
		}
    	mPrevious=mCurrent;
    	mCurrent=current;
    	notifyChange();
    	updateView();
    }
    
    private void validateCurrentView(CharSequence str) {
		int val=getSelectedPos(str.toString());
		if ((val>=mStart)&&(val<=mEnd)) {
			if (mCurrent!=val) {
				mPrevious=mCurrent;
				mCurrent=val;
				notifyChange();
			}
		}
		updateView();
	}
    
    private void validateInput(View v){
    	String str=String.valueOf(((TextView)v).getText());
    	if ("".equals(str)) {
			//Restore to the old value as we don't allow empty values
    		updateView();
		}else {
			//check the new value and ensure it's in range
			validateCurrentView(str);
		}
    }
    
	@Override
	public boolean onLongClick(View v) {
		// TODO Auto-generated method stub
		/*
		 * The text view may still have focus so clear it's focus which will 
		 * trigger the on focus changed and any typed values to be pulled
		 */
		mText.clearFocus();
		if (R.id.increment==v.getId()) {
			mIncrement=true;
			mHandler.post(mRunnable);
		}else if (R.id.decrement==v.getId()) {
			mDecrement=true;
			mHandler.post(mRunnable);
		}
		return true;
	}

	@Override
	public void onFocusChange(View v, boolean hasFocus) {
		/*
		 * When focus is lost check that the text field
		 * has valid values
		 */
		if (!hasFocus) {
			validateInput(v);
		}
	}

	@Override
	public void onClick(View v) {
		// TODO Auto-generated method stub
		validateInput(mText);
		if (!mText.hasFocus()) {
			mText.requestFocus();
			
		}
		//now perform the increment/decrement
		if (R.id.increment==v.getId()) {
			changeCurrent(mCurrent+1);
		}else if (R.id.decrement==v.getId()) {
			changeCurrent(mCurrent-1);
		}
	}
	
	/*
	 * @hide
	 */
	public void cancelIncrement() {
		mIncrement = false;
	}
	
	/*
	 * hide
	 */
	public void cancelDecrement() {
		mDecrement = false;
	}
	
	private static final char[] DIGIT_CHARACTERS = new char[] {
        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
    };
	
	private NumberPickerButton mIncremenButton;
	private NumberPickerButton mDecremenButton;
	
	private class NumberPickerInputFilter implements InputFilter{
		public CharSequence filter(CharSequence source,int start,int end,Spanned dest,int dstart,int dend) {
			if (mDisplayedValues==null) {
				return mNumberInputFilter.filter(source, start, end, dest, dstart, dend);
			}
			CharSequence filtered=String.valueOf(source.subSequence(start, end));
			String result=String.valueOf(dest.subSequence(0, dstart))+filtered+dest.subSequence(dend, dest.length());
			String str=String.valueOf(result).toLowerCase();
			for (String val : mDisplayedValues) {
				val=val.toLowerCase();
				if (val.startsWith(str)) {
					return filtered;
				}
			}
			return "";
		}
	}
	
	private class NumberRangeKeyListener extends NumberKeyListener {

        // XXX This doesn't allow for range limits when controlled by a
        // soft input method!
		
		public int getInputType(){
			return InputType.TYPE_CLASS_NUMBER;
		}
		
		@Override
		protected char[] getAcceptedChars(){
			return DIGIT_CHARACTERS;
		}
		
		@Override
		public CharSequence filter(CharSequence source,int start,int end,Spanned dest,int dstart,int dend) {
			CharSequence filtered=super.filter(source, start, end, dest, dstart, dend);
			if (filtered==null) {
				filtered=source.subSequence(start, end);
			}
			String result=String.valueOf(dest.subSequence(0, dstart))+filtered+dest.subSequence(dend, dest.length());
			if ("".equals(result)) {
				return result;
			}
			int val=getSelectedPos(result);
			
			/* Ensure the user can't type in a value greater
             * than the max allowed. We have to allow less than min
             * as the user might want to delete some numbers
             * and then type a new number.
             */
			if (val>mEnd) {
				return "";
			}else {
				return filtered;
			}
			
		}
	}
	
	private int getSelectedPos(String str) {
		if (mDisplayedValues==null) {
			try {
				return Integer.parseInt(str);
			} catch (NumberFormatException e) {
				// TODO: handle exception
				/*Igonre as if it's not a number we don't care */
			}
		}else {
			for (int i = 0; i < mDisplayedValues.length; i++) {
				/*Don't force the user to type in jan when ja will do*/
				str=str.toLowerCase();
				if (mDisplayedValues[i].toLowerCase().startsWith(str)) {
					return mStart+i;
				}
			}
			/* The user might have typed in a number into the month field i.e.
             * 10 instead of OCT so support that too.
             */
			try {
				return Integer.parseInt(str);
			} catch (NumberFormatException e) {
				// TODO: handle exception
				/*Igonre as if it's not a number we don't care */
			}
		}
		return mStart;
	}

	/**
	 * @return the current value.
	 */
	public int getCurrent() {
		return mCurrent;
	}
}
